package uk.ac.nott.cs.g53dia;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import utils.Constants;

/**
 * An infinite grid of cells representing the current state of the environment
 * at a given timestep.
 * <p>
 * Each cell in the Environment implements the {@link Cell} interface.
 * 
 * @author Neil Madden.
 */
/*
 * Copyright (c) 2003 Stuart Reeves. Copyright (c) 2003-2005 Neil Madden.
 * 
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */
public class Environment {
	/**
	 * Mapping from an (x,y) point to a Cell.
	 */
	private Map<Point, Cell> map;
	/**
	 * Mapping from a Bee to an (x,y) point.
	 */
	private Map<Bee, Point> beeMap;
	 /**
   * Mapping from an (x,y) point to a num of flowers.
   */
  private Map<Point, Integer> maxFlowers;
  /**
   * Mapping from an (x,y) point to a num of flowers.
   */
  private Map<Point, Integer> actFlowers;
	/**
	 * CellFactory which is called to generate new environment.
	 */
	private CellFactory cfactory;
	/**
	 * Current timestep of the simulation.
	 */
	private long timestep;
	/**
	 * Length of a day in time-steps.
	 */
	public final static int DAY_LENGTH = 10000;

	private static Environment INSTANCE;

	/**
	 * Initialises the bee environment.
	 * 
	 * Creates an initial environment of flowers.
	 * 
	 * @param size
	 *            size of the initial environment
	 * @param factory
	 *            factory used to create new cells
	 */
	private Environment(int size, CellFactory factory) {
		this.map = new HashMap<Point, Cell>();
		this.beeMap = new HashMap<Bee, Point>();
		this.maxFlowers = new HashMap<Point, Integer>();
		this.cfactory = factory;
		this.timestep = Constants.STARTING_TIME;

		// Generate initial environment
		for (int x = -size; x <= size; x++) {
			for (int y = -size; y <= size; y++) {
			  Point p = new Point(x, y);
				boolean flower = cfactory.generateCell(this, p);
				if (flower) {
				  maxFlowers.put(p, ((Flower) map.get(p)).getNumFlowers());
				}
			}
		}
	}

	private Environment(int size) {
		this(size, new DefaultCellFactory());
	}

	public static synchronized Environment getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new Environment(Math.min(Constants.GS_INITIAL_X, Constants.GS_INITIAL_Y));
		} 
		return INSTANCE;
	}
	
	public static synchronized void reset() {
			INSTANCE = new Environment(Math.min(Constants.GS_INITIAL_X, Constants.GS_INITIAL_Y));
	}

	/**
	 * Return the cells a bee can see.
	 * 
	 * @param pos
	 *            position of the bee
	 * @param size
	 *            distance the bee can see
	 * @return an array containing the cells visible from pos.
	 */
	public synchronized Cell[][] getView(Point pos, int size) {
		Cell[][] res = new Cell[size * 2 + 1][size * 2 + 1];
		for (int x = pos.x - size; x <= pos.x + size; x++) {
			for (int y = pos.y - size; y <= pos.y + size; y++) {
				int i = x - (pos.x - size);
				int j = (2 * size) - (y - (pos.y - size));
				res[i][j] = getCell(new Point(x, y));
			}
		}
		return res;
	}

	/**
	 * Get the cell at a specified location
	 * 
	 * @param pos
	 *            coordinates of the cell
	 */
	public Cell getCell(Point pos) {
		if (!map.containsKey(pos))
			// No cell at this position, so make a new one
			cfactory.generateCell(this, pos);
		return map.get(pos);
	}

	/**
	 * Get the current timestep.
	 * 
	 * @return the current timestep
	 */
	public long getTimestep() {
		return timestep;
	}

	/**
	 * Increase timestep.
	 */
	public void tick() {
	  actFlowers = new HashMap<Point, Integer>(maxFlowers);
		timestep++;
	}

	/**
	 * Add a cell to the environment.
	 * 
	 * @param c
	 *            cell to be added
	 */
	public synchronized void putCell(Cell c) {
		map.put(c.getPoint(), c);
	}

	public static synchronized void moveBee(Bee bee) {
		getInstance().beeMap.put(bee, bee.getPosition());
	}
	
	public synchronized List<Point> getVisibleBees(Point position, int viewRange) {
		List<Point> result = new ArrayList<Point>();
		for (Point p : getInstance().beeMap.values()) {
			if (!p.equals(position) && Math.abs(p.x-position.x) <= viewRange && Math.abs(p.y-position.y) <= viewRange) {
				result.add((Point) p.clone());
			}
		}
		return result;
	}
	
	public boolean harvestFromFlower(Point p) {
	  if (actFlowers.get(p) == 0) {
	    return false;
	  } else {
	    actFlowers.put(p, actFlowers.get(p) - 1);
	    return true;
	  }
	}
	
	public List<Bee> getBeesForCommunication(Point position, int viewRange) {
		List<Bee> result = new ArrayList<Bee>();
		for (Bee bee : getInstance().beeMap.keySet()) {
			if (Math.abs(beeMap.get(bee).x-position.x) <= viewRange && Math.abs(beeMap.get(bee).y-position.y) <= viewRange) {
				result.add(bee);
			}
		}
		return result;
	}

}
